/*
 * Copyright (c) 1996, 2015, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.beans;

import com.sun.beans.finder.ClassFinder;

import java.applet.Applet;
import java.applet.AppletContext;
import java.applet.AppletStub;
import java.applet.AudioClip;

import java.awt.Image;

import java.beans.beancontext.BeanContext;

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
import java.io.StreamCorruptedException;

import java.lang.reflect.Modifier;

import java.net.URL;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Vector;

/**
 * This class provides some general purpose beans control methods.
 */

public class Beans {

  /**
   * <p>
   * Instantiate a JavaBean.
   * </p>
   *
   * @param cls the class-loader from which we should create the bean.  If this is null, then the
   * system class-loader is used.
   * @param beanName the name of the bean within the class-loader. For example "sun.beanbox.foobah"
   * @return a JavaBean
   * @throws ClassNotFoundException if the class of a serialized object could not be found.
   * @throws IOException if an I/O error occurs.
   */

  public static Object instantiate(ClassLoader cls, String beanName)
      throws IOException, ClassNotFoundException {
    return Beans.instantiate(cls, beanName, null, null);
  }

  /**
   * <p>
   * Instantiate a JavaBean.
   * </p>
   *
   * @param cls the class-loader from which we should create the bean.  If this is null, then the
   * system class-loader is used.
   * @param beanName the name of the bean within the class-loader. For example "sun.beanbox.foobah"
   * @param beanContext The BeanContext in which to nest the new bean
   * @return a JavaBean
   * @throws ClassNotFoundException if the class of a serialized object could not be found.
   * @throws IOException if an I/O error occurs.
   */

  public static Object instantiate(ClassLoader cls, String beanName, BeanContext beanContext)
      throws IOException, ClassNotFoundException {
    return Beans.instantiate(cls, beanName, beanContext, null);
  }

  /**
   * Instantiate a bean.
   * <p>
   * The bean is created based on a name relative to a class-loader.
   * This name should be a dot-separated name such as "a.b.c".
   * <p>
   * In Beans 1.0 the given name can indicate either a serialized object
   * or a class.  Other mechanisms may be added in the future.  In
   * beans 1.0 we first try to treat the beanName as a serialized object
   * name then as a class name.
   * <p>
   * When using the beanName as a serialized object name we convert the
   * given beanName to a resource pathname and add a trailing ".ser" suffix.
   * We then try to load a serialized object from that resource.
   * <p>
   * For example, given a beanName of "x.y", Beans.instantiate would first
   * try to read a serialized object from the resource "x/y.ser" and if
   * that failed it would try to load the class "x.y" and create an
   * instance of that class.
   * <p>
   * If the bean is a subtype of java.applet.Applet, then it is given
   * some special initialization.  First, it is supplied with a default
   * AppletStub and AppletContext.  Second, if it was instantiated from
   * a classname the applet's "init" method is called.  (If the bean was
   * deserialized this step is skipped.)
   * <p>
   * Note that for beans which are applets, it is the caller's responsiblity
   * to call "start" on the applet.  For correct behaviour, this should be done
   * after the applet has been added into a visible AWT container.
   * <p>
   * Note that applets created via beans.instantiate run in a slightly
   * different environment than applets running inside browsers.  In
   * particular, bean applets have no access to "parameters", so they may
   * wish to provide property get/set methods to set parameter values.  We
   * advise bean-applet developers to test their bean-applets against both
   * the JDK appletviewer (for a reference browser environment) and the
   * BDK BeanBox (for a reference bean container).
   *
   * @param cls the class-loader from which we should create the bean.  If this is null, then the
   * system class-loader is used.
   * @param beanName the name of the bean within the class-loader. For example "sun.beanbox.foobah"
   * @param beanContext The BeanContext in which to nest the new bean
   * @param initializer The AppletInitializer for the new bean
   * @return a JavaBean
   * @throws ClassNotFoundException if the class of a serialized object could not be found.
   * @throws IOException if an I/O error occurs.
   */

  public static Object instantiate(ClassLoader cls, String beanName, BeanContext beanContext,
      AppletInitializer initializer)
      throws IOException, ClassNotFoundException {

    InputStream ins;
    ObjectInputStream oins = null;
    Object result = null;
    boolean serialized = false;
    IOException serex = null;

    // If the given classloader is null, we check if an
    // system classloader is available and (if so)
    // use that instead.
    // Note that calls on the system class loader will
    // look in the bootstrap class loader first.
    if (cls == null) {
      try {
        cls = ClassLoader.getSystemClassLoader();
      } catch (SecurityException ex) {
        // We're not allowed to access the system class loader.
        // Drop through.
      }
    }

    // Try to find a serialized object with this name
    final String serName = beanName.replace('.', '/').concat(".ser");
    if (cls == null) {
      ins = ClassLoader.getSystemResourceAsStream(serName);
    } else {
      ins = cls.getResourceAsStream(serName);
    }
    if (ins != null) {
      try {
        if (cls == null) {
          oins = new ObjectInputStream(ins);
        } else {
          oins = new ObjectInputStreamWithLoader(ins, cls);
        }
        result = oins.readObject();
        serialized = true;
        oins.close();
      } catch (IOException ex) {
        ins.close();
        // Drop through and try opening the class.  But remember
        // the exception in case we can't find the class either.
        serex = ex;
      } catch (ClassNotFoundException ex) {
        ins.close();
        throw ex;
      }
    }

    if (result == null) {
      // No serialized object, try just instantiating the class
      Class<?> cl;

      try {
        cl = ClassFinder.findClass(beanName, cls);
      } catch (ClassNotFoundException ex) {
        // There is no appropriate class.  If we earlier tried to
        // deserialize an object and got an IO exception, throw that,
        // otherwise rethrow the ClassNotFoundException.
        if (serex != null) {
          throw serex;
        }
        throw ex;
      }

      if (!Modifier.isPublic(cl.getModifiers())) {
        throw new ClassNotFoundException("" + cl + " : no public access");
      }

            /*
             * Try to instantiate the class.
             */

      try {
        result = cl.newInstance();
      } catch (Exception ex) {
        // We have to remap the exception to one in our signature.
        // But we pass extra information in the detail message.
        throw new ClassNotFoundException("" + cl + " : " + ex, ex);
      }
    }

    if (result != null) {

      // Ok, if the result is an applet initialize it.

      AppletStub stub = null;

      if (result instanceof Applet) {
        Applet applet = (Applet) result;
        boolean needDummies = initializer == null;

        if (needDummies) {

          // Figure our the codebase and docbase URLs.  We do this
          // by locating the URL for a known resource, and then
          // massaging the URL.

          // First find the "resource name" corresponding to the bean
          // itself.  So a serialzied bean "a.b.c" would imply a
          // resource name of "a/b/c.ser" and a classname of "x.y"
          // would imply a resource name of "x/y.class".

          final String resourceName;

          if (serialized) {
            // Serialized bean
            resourceName = beanName.replace('.', '/').concat(".ser");
          } else {
            // Regular class
            resourceName = beanName.replace('.', '/').concat(".class");
          }

          URL objectUrl = null;
          URL codeBase = null;
          URL docBase = null;

          // Now get the URL correponding to the resource name.
          if (cls == null) {
            objectUrl = ClassLoader.getSystemResource(resourceName);
          } else {
            objectUrl = cls.getResource(resourceName);
          }

          // If we found a URL, we try to locate the docbase by taking
          // of the final path name component, and the code base by taking
          // of the complete resourceName.
          // So if we had a resourceName of "a/b/c.class" and we got an
          // objectURL of "file://bert/classes/a/b/c.class" then we would
          // want to set the codebase to "file://bert/classes/" and the
          // docbase to "file://bert/classes/a/b/"

          if (objectUrl != null) {
            String s = objectUrl.toExternalForm();

            if (s.endsWith(resourceName)) {
              int ix = s.length() - resourceName.length();
              codeBase = new URL(s.substring(0, ix));
              docBase = codeBase;

              ix = s.lastIndexOf('/');

              if (ix >= 0) {
                docBase = new URL(s.substring(0, ix + 1));
              }
            }
          }

          // Setup a default context and stub.
          BeansAppletContext context = new BeansAppletContext(applet);

          stub = (AppletStub) new BeansAppletStub(applet, context, codeBase, docBase);
          applet.setStub(stub);
        } else {
          initializer.initialize(applet, beanContext);
        }

        // now, if there is a BeanContext, add the bean, if applicable.

        if (beanContext != null) {
          unsafeBeanContextAdd(beanContext, result);
        }

        // If it was deserialized then it was already init-ed.
        // Otherwise we need to initialize it.

        if (!serialized) {
          // We need to set a reasonable initial size, as many
          // applets are unhappy if they are started without
          // having been explicitly sized.
          applet.setSize(100, 100);
          applet.init();
        }

        if (needDummies) {
          ((BeansAppletStub) stub).active = true;
        } else {
          initializer.activate(applet);
        }

      } else if (beanContext != null) {
        unsafeBeanContextAdd(beanContext, result);
      }
    }

    return result;
  }

  @SuppressWarnings("unchecked")
  private static void unsafeBeanContextAdd(BeanContext beanContext, Object res) {
    beanContext.add(res);
  }

  /**
   * From a given bean, obtain an object representing a specified
   * type view of that source object.
   * <p>
   * The result may be the same object or a different object.  If
   * the requested target view isn't available then the given
   * bean is returned.
   * <p>
   * This method is provided in Beans 1.0 as a hook to allow the
   * addition of more flexible bean behaviour in the future.
   *
   * @param bean Object from which we want to obtain a view.
   * @param targetType The type of view we'd like to get.
   * @return an object representing a specified type view of the source object
   */
  public static Object getInstanceOf(Object bean, Class<?> targetType) {
    return bean;
  }

  /**
   * Check if a bean can be viewed as a given target type.
   * The result will be true if the Beans.getInstanceof method
   * can be used on the given bean to obtain an object that
   * represents the specified targetType type view.
   *
   * @param bean Bean from which we want to obtain a view.
   * @param targetType The type of view we'd like to get.
   * @return "true" if the given bean supports the given targetType.
   */
  public static boolean isInstanceOf(Object bean, Class<?> targetType) {
    return Introspector.isSubclass(bean.getClass(), targetType);
  }

  /**
   * Test if we are in design-mode.
   *
   * @return True if we are running in an application construction environment.
   * @see DesignMode
   */
  public static boolean isDesignTime() {
    return ThreadGroupContext.getContext().isDesignTime();
  }

  /**
   * Determines whether beans can assume a GUI is available.
   *
   * @return True if we are running in an environment where beans can assume that an interactive GUI
   * is available, so they can pop up dialog boxes, etc.  This will normally return true in a
   * windowing environment, and will normally return false in a server environment or if an
   * application is running as part of a batch job.
   * @see Visibility
   */
  public static boolean isGuiAvailable() {
    return ThreadGroupContext.getContext().isGuiAvailable();
  }

  /**
   * Used to indicate whether of not we are running in an application
   * builder environment.
   *
   * <p>Note that this method is security checked
   * and is not available to (for example) untrusted applets.
   * More specifically, if there is a security manager,
   * its <code>checkPropertiesAccess</code>
   * method is called. This could result in a SecurityException.
   *
   * @param isDesignTime True if we're in an application builder tool.
   * @throws SecurityException if a security manager exists and its <code>checkPropertiesAccess</code>
   * method doesn't allow setting of system properties.
   * @see SecurityManager#checkPropertiesAccess
   */

  public static void setDesignTime(boolean isDesignTime)
      throws SecurityException {
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
      sm.checkPropertiesAccess();
    }
    ThreadGroupContext.getContext().setDesignTime(isDesignTime);
  }

  /**
   * Used to indicate whether of not we are running in an environment
   * where GUI interaction is available.
   *
   * <p>Note that this method is security checked
   * and is not available to (for example) untrusted applets.
   * More specifically, if there is a security manager,
   * its <code>checkPropertiesAccess</code>
   * method is called. This could result in a SecurityException.
   *
   * @param isGuiAvailable True if GUI interaction is available.
   * @throws SecurityException if a security manager exists and its <code>checkPropertiesAccess</code>
   * method doesn't allow setting of system properties.
   * @see SecurityManager#checkPropertiesAccess
   */

  public static void setGuiAvailable(boolean isGuiAvailable)
      throws SecurityException {
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
      sm.checkPropertiesAccess();
    }
    ThreadGroupContext.getContext().setGuiAvailable(isGuiAvailable);
  }
}

/**
 * This subclass of ObjectInputStream delegates loading of classes to
 * an existing ClassLoader.
 */

class ObjectInputStreamWithLoader extends ObjectInputStream {

  private ClassLoader loader;

  /**
   * Loader must be non-null;
   */

  public ObjectInputStreamWithLoader(InputStream in, ClassLoader loader)
      throws IOException, StreamCorruptedException {

    super(in);
    if (loader == null) {
      throw new IllegalArgumentException("Illegal null argument to ObjectInputStreamWithLoader");
    }
    this.loader = loader;
  }

  /**
   * Use the given ClassLoader rather than using the system class
   */
  @SuppressWarnings("rawtypes")
  protected Class resolveClass(ObjectStreamClass classDesc)
      throws IOException, ClassNotFoundException {

    String cname = classDesc.getName();
    return ClassFinder.resolveClass(cname, this.loader);
  }
}

/**
 * Package private support class.  This provides a default AppletContext
 * for beans which are applets.
 */

class BeansAppletContext implements AppletContext {

  Applet target;
  Hashtable<URL, Object> imageCache = new Hashtable<>();

  BeansAppletContext(Applet target) {
    this.target = target;
  }

  public AudioClip getAudioClip(URL url) {
    // We don't currently support audio clips in the Beans.instantiate
    // applet context, unless by some luck there exists a URL content
    // class that can generate an AudioClip from the audio URL.
    try {
      return (AudioClip) url.getContent();
    } catch (Exception ex) {
      return null;
    }
  }

  public synchronized Image getImage(URL url) {
    Object o = imageCache.get(url);
    if (o != null) {
      return (Image) o;
    }
    try {
      o = url.getContent();
      if (o == null) {
        return null;
      }
      if (o instanceof Image) {
        imageCache.put(url, o);
        return (Image) o;
      }
      // Otherwise it must be an ImageProducer.
      Image img = target.createImage((java.awt.image.ImageProducer) o);
      imageCache.put(url, img);
      return img;

    } catch (Exception ex) {
      return null;
    }
  }

  public Applet getApplet(String name) {
    return null;
  }

  public Enumeration<Applet> getApplets() {
    Vector<Applet> applets = new Vector<>();
    applets.addElement(target);
    return applets.elements();
  }

  public void showDocument(URL url) {
    // We do nothing.
  }

  public void showDocument(URL url, String target) {
    // We do nothing.
  }

  public void showStatus(String status) {
    // We do nothing.
  }

  public void setStream(String key, InputStream stream) throws IOException {
    // We do nothing.
  }

  public InputStream getStream(String key) {
    // We do nothing.
    return null;
  }

  public Iterator<String> getStreamKeys() {
    // We do nothing.
    return null;
  }
}

/**
 * Package private support class.  This provides an AppletStub
 * for beans which are applets.
 */
class BeansAppletStub implements AppletStub {

  transient boolean active;
  transient Applet target;
  transient AppletContext context;
  transient URL codeBase;
  transient URL docBase;

  BeansAppletStub(Applet target,
      AppletContext context, URL codeBase,
      URL docBase) {
    this.target = target;
    this.context = context;
    this.codeBase = codeBase;
    this.docBase = docBase;
  }

  public boolean isActive() {
    return active;
  }

  public URL getDocumentBase() {
    // use the root directory of the applet's class-loader
    return docBase;
  }

  public URL getCodeBase() {
    // use the directory where we found the class or serialized object.
    return codeBase;
  }

  public String getParameter(String name) {
    return null;
  }

  public AppletContext getAppletContext() {
    return context;
  }

  public void appletResize(int width, int height) {
    // we do nothing.
  }
}
